---
title: Matchers
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

Matchers are the backbone of Interweave as they allow arbitrary insertion of React elements into
strings, through the use of regex pattern matching. This feature is quite powerful as it opens up
many possibilities.

It works by matching patterns within a string, deconstructing it into tokens, and reconstructing it
back into an array of strings and React elements, therefore, permitting it to be rendered by React's
virtual DOM layer. For example, take the following string "Check out my website,
github.com/milesj!", and a `UrlMatcher`, you'd get the following array.

```tsx
['Check out my website, ', <Url>github.com/milesj</Url>, '!'];
```

## Usage

Matchers can be passed to each render of `<Interweave />`. When adding a matcher, a unique
camel-case name must be passed to the constructor.

```tsx
<Interweave matchers={[new CustomMatcher('foo')]} />
```

To disable all matchers, per render, pass the `disableMatchers` prop.

```tsx
<Interweave disableMatchers />
```

To disable a single matcher, you can pass a prop that starts with "no", and ends with the unique
name of the matcher (the one passed to the constructor). Using the example above, you can pass a
`noFoo` prop.

```tsx
<Interweave noFoo />
```

## Creating a matcher

To create a custom matcher, implement a class that extends the base `Matcher` class, or use a plain
object. Both approaches will require the following methods to be defined (excluding callbacks).

- `match(string: string)` - Match the passed string using a regex pattern. This method must return
  `null` if no match is found (will abort matching), else it must return an object with the
  properties below. Any additional keys defined in this object will be passed as props to the
  rendered element.
  - `index` (`number`) - The starting index in which the match was found (is provided by the native
    `String#match()`).
  - `length` (`number`) - The original length of the match, before it was potentially modified.
  - `match` (`string`) - The content that was matched (is usually the 0-index in the result).
  - `valid` (`boolean`) - Whether the match is valid or not. This can be used to control false
    positives.
  - `void` (`boolean`) - Mark the result as a void element. Cannot be nested by other matchers, nor
    contain children (is self closing).
- `replaceWith(children: ChildrenNode, props: object)` - Returns a React element that replaces the
  matched content in the string. The parsed children are passed as the 1st argument, and any matched
  props or parent props are passed as the 2nd argument.
- `createElement(children: ChildrenNode, props: object)` - The same as `replaceWith` but used in
  object matchers.
- `asTag()` - The HTML tag name of the replacement element.
- `onBeforeParse(content: string, props: object)` - Callback that fires before parsing. Is passed
  the source string and must return a string.
- `onAfterParse(nodes: Node[], props: object)` - Callback that fires after parsing. Is passed an
  array of strings/elements and must return an array.

> Using the plain object approach requires more implementation and a higher overhead.

<Tabs
  groupId="matcher-type"
  defaultValue="class"
  values={[
    { label: 'Class', value: 'class' },
    { label: 'Object', value: 'object' },
  ]}
>
  <TabItem value="class">

```tsx
import { Matcher, MatchResponse, Node } from 'interweave';

class CustomMatcher extends Matcher<CustomProps> {
	match(string: string): MatchResponse<{ extraProp: string }> | null {
		const result = string.match(/foo/);

		if (!result) {
			return null;
		}

		return {
			index: result.index!,
			length: result[0].length,
			match: result[0],
			extraProp: 'foo', // or result[1], etc
			valid: true,
		};
	}

	replaceWith(children: ChildrenNode, props: CustomProps): Node {
		return <span {...props}>{children}</span>;
	}

	asTag(): string {
		return 'span';
	}
}

const matcher = new CustomMatcher('foo');
```

  </TabItem>
  <TabItem value="object">

```tsx
import { MatcherInterface } from 'interweave';

const matcher: MatcherInterface<CustomProps> = {
	inverseName: 'noFoo',
	propName: 'foo',
	match(string) {
		const result = string.match(/foo/);

		if (!result) {
			return null;
		}

		return {
			index: result.index!,
			length: result[0].length,
			match: result[0],
			extraProp: 'foo', // or result[1], etc
			valid: true,
		};
	},
	createElement(children, props) {
		return <span {...props}>{children}</span>;
	},
	asTag() {
		return 'span';
	},
};
```

  </TabItem>
</Tabs>

To ease the matching process, there is a `doMatch()` method on `Matcher` that handles the `null` and
object building logic. Simply pass it a regex pattern and a callback to build the object.

```ts
class CustomMatcher extends Matcher<CustomProps> {
	// ...

	match(string: string): MatchResponse<{ extraProp: string }> | null {
		return this.doMatch(string, /foo/, (matches) => ({
			extraProp: 'foo',
		}));
	}
}
```

> When the matcher finds a match, the parser will temporarily wrap the match in a token that looks
> like the following: `{{{foo1}}}matched content{{{/foo1}}}`. This token _is present_ for subsequent
> matchers, so be weary of the patterns you're attempting to match, as they may capture the
> temporary tokens.

## Rendered elements

When a match is found, a React element is rendered (from a React component) from either the
matcher's `replaceWith()` method, or from a factory. What's a factory you ask? Simply put, it's a
component reference passed to the constructor of a matcher, allowing the rendered element to be
customized for built-in or third-party matchers.

```ts
new CustomMatcher('foo', {}, SomeComponent);
```

> Elements returned from `replaceWith()` or the factory must return an HTML element with the same
> tag name as defined by `asTag()`.

## Greedy matching

By default, a matcher will continually run until it exhausts all matches. However, each iteration
will shorten the string being matched until there is no more. This may be problematic if your
matcher uses multiple patterns, as they can happen in any order, or can be found at any point in the
string.

To work around this, a matcher can be marked as greedy to continually run against the whole string
each iteration, until fully exhausted. Set the `greedy` property to enable.

<Tabs
  groupId="matcher-type"
  defaultValue="class"
  values={[
    { label: 'Class', value: 'class' },
    { label: 'Object', value: 'object' },
  ]}
>
  <TabItem value="class">

```tsx
import { Matcher } from 'interweave';

class CustomMatcher extends Matcher<CustomProps> {
	greedy: boolean = true;

	// ...
}
```

  </TabItem>
  <TabItem value="object">

```tsx
import { MatcherInterface } from 'interweave';

const matcher: MatcherInterface<CustomProps> = {
	greedy: true,

	// ...
};
```

  </TabItem>
</Tabs>
